/*
 * Copyright 2011-2014 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.dianwoba.core.redis.operation.impl;

import java.util.Set;

import com.dianwoba.core.redis.JedisCallback;
import com.dianwoba.core.redis.JedisTemplate;
import com.dianwoba.core.redis.operation.ZSetOperations;

import redis.clients.jedis.BinaryJedisCommands;
import redis.clients.jedis.Tuple;

/**
 * Default implementation of {@link ZSetOperations}.
 * 
 */
public class DefaultZSetOperations<K, V, X extends BinaryJedisCommands> 
    extends AbstractOperations<K, V, X> implements ZSetOperations<K, V> {

	public DefaultZSetOperations(JedisTemplate<K, V, X> template) {
		super(template);
	}

	public Long add(final K key, final V value, final double score) {
		final byte[] rawKey = rawKey(key);
		final byte[] rawValue = rawValue(value);

		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				return jedis.zadd(rawKey, score, rawValue);
			}
		});
	}

	public Double incrementScore(K key, V value, final double delta) {
		final byte[] rawKey = rawKey(key);
		final byte[] rawValue = rawValue(value);

		return execute(new JedisCallback<Double, X>() {

			public Double doInRedis(X jedis) {
				return jedis.zincrby(rawKey, delta, rawValue);
			}
		});
	}

	public Set<V> range(K key, final long start, final long end) {
		final byte[] rawKey = rawKey(key);

		Set<byte[]> rawValues = execute(new JedisCallback<Set<byte[]>, X>() {

			public Set<byte[]> doInRedis(X jedis) {
				return jedis.zrange(rawKey, start, end);
			}
		});

		return deserializeValues(rawValues);
	}

	public Set<V> reverseRange(K key, final long start, final long end) {
		final byte[] rawKey = rawKey(key);

		Set<byte[]> rawValues = execute(new JedisCallback<Set<byte[]>, X>() {

			public Set<byte[]> doInRedis(X jedis) {
				return jedis.zrevrange(rawKey, start, end);
			}
		});

		return deserializeValues(rawValues);
	}

	public Set<TypedTuple<V>> rangeWithScores(K key, final long start, final long end) {
		final byte[] rawKey = rawKey(key);
		Set<Tuple> rawValues = execute(new JedisCallback<Set<Tuple>, X>() {
			public Set<Tuple> doInRedis(X jedis) {
				return jedis.zrangeWithScores(rawKey, start, end);
			}
		});
		return deserializeTupleValues(rawValues);
	}

	public Set<TypedTuple<V>> reverseRangeWithScores(K key, final long start, final long end) {
		final byte[] rawKey = rawKey(key);

		Set<Tuple> rawValues = execute(new JedisCallback<Set<Tuple>, X>() {

			public Set<Tuple> doInRedis(X jedis) {
				return jedis.zrevrangeWithScores(rawKey, start, end);
			}
		});

		return deserializeTupleValues(rawValues);
	}

	public Set<V> rangeByScore(K key, final double min, final double max) {
		final byte[] rawKey = rawKey(key);

		Set<byte[]> rawValues = execute(new JedisCallback<Set<byte[]>, X>() {

			public Set<byte[]> doInRedis(X jedis) {
				return jedis.zrangeByScore(rawKey, min, max);
			}
		});

		return deserializeValues(rawValues);
	}

	public Set<V> rangeByScore(K key, final double min, final double max, final int offset, final int count) {
		final byte[] rawKey = rawKey(key);

		Set<byte[]> rawValues = execute(new JedisCallback<Set<byte[]>, X>() {

			public Set<byte[]> doInRedis(X jedis) {
				return jedis.zrangeByScore(rawKey, min, max, offset, count);
			}
		});

		return deserializeValues(rawValues);
	}

	public Set<V> reverseRangeByScore(K key, final double min, final double max) {
		final byte[] rawKey = rawKey(key);

		Set<byte[]> rawValues = execute(new JedisCallback<Set<byte[]>, X>() {

			public Set<byte[]> doInRedis(X jedis) {
				return jedis.zrevrangeByScore(rawKey, min, max);
			}
		});

		return deserializeValues(rawValues);
	}

	public Set<V> reverseRangeByScore(K key, final double min, final double max, final int offset, final int count) {
		final byte[] rawKey = rawKey(key);

		Set<byte[]> rawValues = execute(new JedisCallback<Set<byte[]>, X>() {

			public Set<byte[]> doInRedis(X jedis) {
				return jedis.zrevrangeByScore(rawKey, min, max, offset, count);
			}
		});

		return deserializeValues(rawValues);
	}

	public Set<TypedTuple<V>> rangeByScoreWithScores(K key, final double min, final double max) {
		final byte[] rawKey = rawKey(key);

		Set<Tuple> rawValues = execute(new JedisCallback<Set<Tuple>, X>() {

			public Set<Tuple> doInRedis(X jedis) {
				return jedis.zrangeByScoreWithScores(rawKey, min, max);
			}
		});

		return deserializeTupleValues(rawValues);
	}

	public Set<TypedTuple<V>> rangeByScoreWithScores(K key, final double min, final double max, final int offset,
			final int count) {
		final byte[] rawKey = rawKey(key);

		Set<Tuple> rawValues = execute(new JedisCallback<Set<Tuple>, X>() {

			public Set<Tuple> doInRedis(X jedis) {
				return jedis.zrangeByScoreWithScores(rawKey, min, max, offset, count);
			}
		});

		return deserializeTupleValues(rawValues);
	}

	public Set<TypedTuple<V>> reverseRangeByScoreWithScores(K key, final double min, final double max) {
		final byte[] rawKey = rawKey(key);

		Set<Tuple> rawValues = execute(new JedisCallback<Set<Tuple>, X>() {

			public Set<Tuple> doInRedis(X jedis) {
				return jedis.zrevrangeByScoreWithScores(rawKey, min, max);

			}
		});

		return deserializeTupleValues(rawValues);
	}

	public Set<TypedTuple<V>> reverseRangeByScoreWithScores(K key, final double min, final double max, final int offset,
			final int count) {
		final byte[] rawKey = rawKey(key);

		Set<Tuple> rawValues = execute(new JedisCallback<Set<Tuple>, X>() {

			public Set<Tuple> doInRedis(X jedis) {
				return jedis.zrevrangeByScoreWithScores(rawKey, min, max, offset, count);

			}
		});

		return deserializeTupleValues(rawValues);
	}

	public Long rank(K key, Object o) {
		final byte[] rawKey = rawKey(key);
		final byte[] rawValue = rawValue(o);

		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				Long zRank = jedis.zrank(rawKey, rawValue);
				return (zRank != null && zRank.longValue() >= 0 ? zRank : null);
			}
		});
	}

	public Long reverseRank(K key, Object o) {
		final byte[] rawKey = rawKey(key);
		final byte[] rawValue = rawValue(o);

		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				Long zRank = jedis.zrevrank(rawKey, rawValue);
				return (zRank != null && zRank.longValue() >= 0 ? zRank : null);
			}
		});
	}

	public Long remove(K key, Object... values) {
		final byte[] rawKey = rawKey(key);
		final byte[][] rawValues = rawValues(values);

		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				return jedis.zrem(rawKey, rawValues);
			}
		});
	}

	public Long removeRange(K key, final long start, final long end) {
		final byte[] rawKey = rawKey(key);
		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				return jedis.zremrangeByRank(rawKey, start, end);
			}
		});
	}

	public Long removeRangeByScore(K key, final double min, final double max) {
		final byte[] rawKey = rawKey(key);
		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				return jedis.zremrangeByScore(rawKey, min, max);
			}
		});
	}

	public Double score(K key, Object o) {
		final byte[] rawKey = rawKey(key);
		final byte[] rawValue = rawValue(o);

		return execute(new JedisCallback<Double, X>() {

			public Double doInRedis(X jedis) {
				return jedis.zscore(rawKey, rawValue);
			}
		});
	}

	public Long count(K key, final double min, final double max) {
		final byte[] rawKey = rawKey(key);

		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				return jedis.zcount(rawKey, min, max);
			}
		});
	}

	@Override
	public Long size(K key) {
		return zCard(key);
	}

	@Override
	public Long zCard(K key) {

		final byte[] rawKey = rawKey(key);
		return execute(new JedisCallback<Long, X>() {

			public Long doInRedis(X jedis) {
				return jedis.zcard(rawKey);
			}
		});
	}

}
